Technical Note TN1060
Controlling Apps with Synthesized Events, or jGNEFilter - the Untold Story

CONTENTS

Until now, jGNEFilter has been "under documented," with only vague mentions appearing in Technote TB 11: GetNextEvent; Blinking Apple Menu. jGNEFilter is the name of a mechanism by which programs can obtain access to each EventRecord just before the event is sent to the caller of GetNextEvent or WaitNextEvent.

Using jGNEFilter, your programs can customize most event-driven interaction with the user, including but not limited to such things as monitoring keystrokes, and programmatically simulating some kinds of user activity. Also, without being an application or driver, your program can arrange to be called periodically at a time when it's safe to call Memory Manager (and the high-level managers which depend on Memory Manager).

Developers who would like to make use of jGNEFilter - or developers who are already bravely making use of it even in the face of inadequate documentation - should read this Technote.

 Updated: [Jan 18 2000]






jGNEFilter Fundamentals

The interface to jGNEFilter is, unfortunately, rather primitive. The key to the whole thing is a single long word in low memory (at address 0x029A, to be precise). This long word, if non-zero (and it's almost always is non-zero; more on that later), is the address of a routine that GetNextEvent calls to filter events. It's that simple.

The process of installing a jGNEFilter routine amounts to saving off the old filter routine address and installing a new one. There is no arbitration for access to this memory location; programs must be very careful to access it according to the calling conventions, as explained in the next section of this Technote. Otherwise, the system may begin to misbehave in mysterious ways and other jGNEFilter routines may not get the access to events they need to function properly.

As is always the case with low memory, you should access jGNEFilter only through the low memory accessor functions declared in the Universal Headers' "LowMem.h", which in this case are LMGetGNEFilter and LMSetGNEFilter.

All jGNEFilter routines should call any previous routine. This policy alone is responsible for the formation of a "chain" of jGNEFilter routines. This is roughly the same idea as a trap patch. Calling the previous jGNEFilter is essential to the proper operation of the Mac and cannot be omitted. Exactly when in your routine to call the next routine in the chain is up to you.

Back to top

jGNEFilter Calling Conventions

jGNEFilter calling conventions are inherently 68K-oriented and don't conform to the calling conventions of any high-level language.

For PowerPC filter routines, use NewGetNextEventFilterProc and CallGetNextEventFilterProc, both of which make use of a special-case routine descriptor that makes writing the routine's interface in a high-level language simple.

For 68K filter routines, it's probably not impossible to write a jGNEFilter routine entirely in a high-level language (assuming you're using a reasonably modern compiler), but it's probably more effort than it's worth. Instead, you will probably want to use a few lines of assembly glue to call a routine written in a higher-level language.

On entry to your jGNEFilter glue, register A1 will contain the address of the event record to be filtered. Register D0 will contain a word which is the proposed return value for GetNextEvent. The word at offset 4 from register A7 (just above the return address) will also be the proposed return value for GetNextEvent. The difference is that D0 is an input value and the stack word is an output value. The stack word will be returned to the caller of GetNextEvent. Initially, the word in D0 and the word on the stack are (or should be, assuming there are no buggy jGNEFilter functions in the chain) the same.


Compatibility Warning:
jGNE code looking for nullEvents by checking the stack-based Boolean or the contents of register D0 may not always work correctly in Mac OS 8.5 or later. To find nullEvents, such code should examine the "what" field in the event record itself rather than looking at the stack-based Boolean or the value stored in register D0.

Since some third-party extensions may exist that do not set up these values correctly when handling nullEvents, this warning may also apply to jGNEFilter functions operating in the context of Mac OS system software prior to Mac OS 8.5.


It's important to note that the values of both registers A1 and D0 must be set before calling the next routine in the chain or returning. This isn't anything particularly special to jGNEFilter routines (as opposed to trap patches and other such things), but it warrants emphasis because it's an easy thing to forget.

By far, the trickiest part of calling the previous jGNEFilter routine is knowing when and how to set the value of the word on the stack. If your routine is going to jump to the next routine in the chain (using a 68K JMP instruction or its equivalent), you need to make sure the stack value is what you would have returned in case the next routine in the chain chooses not to change it. If your routine is going to call the next routine in the chain (using a 68K JSR instruction or its equivalent), you should push a word onto the stack before calling the next routine in the chain and pop the word after the routine returns. You can then use the return value from the next routine in the chain routine to help you decide what return value to put on the stack when your routine returns.

In any case, you should set register D0 to the value you want the next routine in the chain to use as input.

    MOVE.W     D0,-(A7)    ;; push pre-result for C
    MOVE.L     A1,-(A7)    ;; push event record pointer for C
    JSR        myGNE       ;; do the real work (in C)
    MOVE.L     (A7)+,A1    ;; restore event record pointer
    ADDQ.L     #2,A7       ;; pop pre-result
                           ;; the post-result (from C) is in D0
    ASL.W      #8,D0       ;; pretend to have same bug as GetNextEvent
    MOVE.W     D0,4(A7)    ;; stash result where caller expects it

Listing 1. 68K assembly sequence for dispatching jGNE calls to C.


Listing 1, slightly modified from the "jGNE Helper" sample on the Developer CD Series Tool Chest Edition, illustrates the correct sequence of 68K assembly instructions for dispatching jGNE calls to a routine written in the C language.

Back to top

jGNEFilter "Gotchas"

The following section discusses the two important edge cases that you need to take into consideration when installing a jGNEFilter.

The Low Memory Context Switching Gotcha

Most low-memory global variables are swapped in and out of low memory on a process-by-process basis. (Their values live in the Process Manager's storage while they're swapped out.) This was done in the early days of MultiFinder to appease applications that assumed they owned the whole machine.

jGNEFilter is not one of the low-memory globals which is swapped. Consequently, it's possible for an application to install a filter routine and get access to events which are about to be passed to other applications. (Mostly, such filters get access to all relevant events destined for the foreground application but only null and update events destined for background applications, because these events are generally the only events which background applications receive.)

An application installing a jGNEFilter function does not pose a problem until it's time to quit the application and/or uninstall the filter. Since the jGNEFilter routine address is just a long word in low memory, there's no way to prevent a second application from reading it and installing a new filter routine address. This second application would expect to call what it perceives to be the next filter routine in the chain. When the time came for the first application to quit, it would restore the "next" routine address low memory, over-writing the filter routine address of the second application. Suddenly, the second application would be excluded from the filter chain and would stop functioning properly. The situation would get even worse if the second application were to call its "next" filter routine address, because that code would have disappeared when the first application quit.

The solution for applications that want to install a jGNEFilter is to install it indirectly via a "jump island" in a 6-byte pointer block in the system heap. Disassembled, the absolute jump instruction is shown in Listing 2.

    JMP        XXXXXXXX    ;; where XXXXXXXX is the address of your filter
routine
                           ;; machine language = 0x4EF9 0x00000000
                           ;; where 0x00000000 is the address

Listing 2. An absolute jump instruction.

You declare a struct for storing an absolute jump instruction as shown in listing 3.

#if PRAGMA_ALIGN_SUPPORTED
#    pragma options align=mac68k
#endif
 typedef struct
{
    unsigned short    jmp;
    void    *addr;
}
tJumpIsland, *tJumpIslandP;
 #if PRAGMA_ALIGN_SUPPORTED
#    pragma options align=reset
#endif

Listing 3. Type declaration suitable for creating a "jump island."

After calling NewSysPtr to allocate the block, you set JMP to 0x4EF9 and addr to the address of your filter routine (or GetNextEventFilterUPP). Remember to flush the instruction cache after performing this magic. (See Technote HW 06 for details on flushing the instruction cache.) When you want to uninstall your filter routine, simply set addr to the previous filter routine address. Do not dispose the pointer block and do not call LMSetGNEFilter, so that other programs continue to function. Do remember to flush the instruction cache again.

The Unexpectedly NIL Filter Routine Address Gotcha

Since the system uses jGNEFilter to do housekeeping such as servicing the Notification Manager queue, one might expect jGNEFilter to always be non-NIL. However, this is not the case. Some third-party programs have taken it upon themselves to set the jGNEFilter routine address to NIL temporarily for their own nefarious purposes. Always be ready to compensate for this. Compensating might be as simple as testing the address before calling it.

The Re-entrancy Gotcha

jGNEFilter is not a loop but a filter. Consequently, a jGNEFilter routine does not have the freedom to define the way in which it handles events. It handles them when the system dictates. If your routine makes an Event Manager call which results in another call to jGNEFilter, your routine needs to set and/or test a re-entrancy flag to avoid infinite recursion.

Back to top

jGNEFilter Limitations

Compared to other mechanisms, jGNEFilters have remarkably few limitations. jGNEFilter routines can allocate or move memory (directly or indirectly), call the Toolbox, perform file I/O, launch applications, etc. The limitations are:

Not a Process

Any system call that relies on the current process to establish some sort of unique identity is not going to work very well. The reason is that a jGNEFilter routine can be called while any process is current.

For example, the AppleEvent Manager uses the current process to identify the sender of an Apple event. A jGNEFilter routine can send Apple events, as long as the current process has its modeHighLevelEventAware bit set, but it can't receive them, and that includes queued reply events. It might be tempting to set up Apple event handler routines while a given process is current, but that's likely to cause you big compatibility problems in the long term, if not right away - just don't do it.

If you need a process, consider starting up a background-only application, either via LaunchApplication or by changing your 'INIT' file to an 'appe'. You can find more information on background-only applications in Technote PS 02 - Background-Only Applications.

International Keystrokes

"Fake" keyDown events are not sent through the jGNEFilter chain. This was a known bug, but it was fixed, and should only manifest itself when multi-byte script packages, such as the Japanese Language Kit, are installed.

If your program needs access to key strokes and will be sold into a market where WorldScript is in heavy use, you may be better off patching WaitNextEvent than writing a jGNEFilter routine. (Yes, it's shocking to see DTS speaking in a favorable light about a trap patch, and it pains us to write it, but it's the plain truth.) Unfortunately, this kind of patch is difficult to write.

Text Services Manager Bugs

In the presence of a Text Services Manager input-method window (usually called, simply, a TSM window), some mouseDown events may not be sent to the jGNEFilter and in fact may appear to "pass through" a TSM window into whatever is behind it. Unfortunately, there is no official work around for this known bug.

Back to top

Summary

The jGNEFilter is a powerful mechanism that adds new functionality to your code, enabling you to watch the system as whole. If you're trying to monitor or modify the user's interaction with the system, jGNEFilter is the place to start. If you're simply trying to achieve a more flexible environment for your periodic tasks - i.e., allowing them to allocate memory or do file I/O, jGNEFilter may also be for you. There are some limitations, as explained in this Technote, but the unique advantages of jGNEFilter outweigh its disadvantages.

Back to top

References

Technote TB 11: GetNextEvent; Blinking Apple Menu

Technote HW 06: Cache As Cache Can

Technote PS 02: Background-Only Applications

Back to top

Change History

01-July-1996

Originally written.

01-February-1997

Corrected assembly snippet.

01-September-1997

Removed obsolete reference to Mac OS 8.

18-January-2000

Added compatiblity warning for nullEvents, reformatted.


Back to top

Downloadables

Acrobat

Acrobat version of this Note (84K).

Download

Acrobat

The "jGNE Helper" Source Code Sample.

Download

Back to top


Developer Documentation | Technical Q&As | Development Kits | Sample Code